Tasks, tasks, and more tasks! Parallel programs are built upon tasks.
In the TPL, tasks are wrappers for parallel operations that are later
queued and scheduled on threads in the .NET Framework 4 thread pool,
which is the default scheduler. As such, tasks are the central
ingredient of a parallel program; tasks replace threads as the basic
unit of execution. Tasks are part of the additional complexity found in
parallel code. For these reasons, debugging a parallel application often
begins with debugging tasks.
Visual Studio 2010 introduces the Parallel Tasks window for
monitoring and debugging tasks. The Parallel Tasks window is similar to
the Threads window in its look and feel and functionality. For those
familiar with the Threads window, transitioning to the Parallel Tasks
window should be simple. The next image shows the Parallel Tasks window.
The Parallel Tasks window displays several columns. Some of these columns are identical to those in the Threads window:
-
First column (no heading)
. You can flag a task in the first column. Flagging is used to group, highlight, or filter tasks.
-
Second column (no heading)
. This is the active task column. A yellow arrow
indicates the current task. If a white arrow appears in this column, it
indicates the task that has been interrupted by the debugger.
-
ID
. The ID is the task identifier.
-
Status
. Status is the execution state of the task, such as
running, waiting, or deadlocked. The appropriate icon is displayed at
the left end of the column.
-
Location
. This column shows the location of the task in the call
stack. If you point to an entry in this column, the entire call stack
for the task is displayed. The entry point method for the task is
displayed at the bottom of the call stack (see the next image).
-
Task
. This is the entry point method and argument for the task.
-
Thread Assignment
. Tasks run on threads. This column displays the name and thread identifier of that thread.
-
AppDomain
. This identifies the application domain for the task.
Click a column heading
to sort on that column, or drag a column heading to change the column
order. You can use the context menu to select additional columns, such
as the Task and AppDomain columns. You can also group tasks on a
particular column. Choose Group By ColumnName from the context menu. In the following image, the tasks are grouped by status.
Developers often struggle to resolve deadlock conditions. Here, the
Parallel Tasks window comes to the rescue! In this example, both Task 2
and Task 3 are deadlocked on something. What is the reason for the
deadlock? In the next exercise, you will discover the answer to that
question.
Use the Parallel Tasks window to find the source of a deadlock
-
If you have not done so already, create a console application for
Microsoft Visual C# in Visual Studio 2010.
-
Build the application and start debugging. A breakpoint instruction
is programmatically embedded in the application. The program will
automatically break at the hardcoded breakpoint.
-
From the Debug menu, choose Windows. Open the Parallel Tasks window.
Use the context menu, as shown earlier, to group tasks on the Status
column.
-
Now that the columns are grouped by status, you can easily observe
that Task 2 and Task 3 are deadlocked. Point to the status column for
Task 2. As shown in the following image, a tooltip is displayed that
indicates that Task 2 is waiting for a System.Object owned by thread 7992. Upon examining the call stack, you see that Task 3 is running on that thread.
Note
The task and thread numbers might vary on your machine.
-
Point to the status for Task 3. Once again, you’ll see a tooltip. Task 3 is also blocked and waiting for a System.Object. This object is owned by thread 4780.
-
Evidence points to a deadlock because Task 2 and Task 3 are both
blocked and waiting for each other. You can examine the source code to
confirm this. In the Parallel Tasks window, open the context menu for
Task 3. Select Switch To Task. This will jump to the actual source code
for that task (see the next image). The final statement executed for the
current stack frame is highlighted, which is the Monitor.Enter method on the s2 object as a parameter. This is where the task deadlocked. You should note that the task has already acquired a lock for the s1 object.
-
Repeat the steps for Task 2 to view the source code (see the following image). You should see that this task has stopped on a Monitor.Enter method for the s1 object, which is the same object that Task 3 already owns. Aha!
This confirms that Task 2 and Task 3 are hopelessly deadlocked on each
other. Admittedly this example is somewhat contrived. However, it
demonstrates many of the features of the new Parallel Tasks window.